///
// * jdbm LICENSE v1.00
// *
// * Redistribution and use of this software and associated documentation
// * ("Software"), with or without modification, are permitted provided
// * that the following conditions are met:
// *
// * 1. Redistributions of source code must retain copyright
// *    statements and notices.  Redistributions must also contain a
// *    copy of this document.
// *
// * 2. Redistributions in binary form must reproduce the
// *    above copyright notice, this list of conditions and the
// *    following disclaimer in the documentation and/or other
// *    materials provided with the distribution.
// *
// * 3. The name "jdbm" must not be used to endorse or promote
// *    products derived from this Software without prior written
// *    permission of Cees de Groot.  For written permission,
// *    please contact cg@cdegroot.com.
// *
// * 4. Products derived from this Software may not be called "jdbm"
// *    nor may "jdbm" appear in their names without prior written
// *    permission of Cees de Groot.
// *
// * 5. Due credit should be given to the jdbm Project
// *    (http://jdbm.sourceforge.net/).
// *
// * THIS SOFTWARE IS PROVIDED BY THE ndbm PROJECT AND CONTRIBUTORS
// * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
// * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
// * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// * OF THE POSSIBILITY OF SUCH DAMAGE.
// *
// * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
// * Contributions are Copyright (C) 2000 by their associated contributors.
// *
// * $Id: PhysicalRowIdManager.java,v 1.3 2003/03/21 03:00:09 boisvert Exp $
// 

//*************************************************************************
//**  Included in JDMB 1.0 port to SharpDBM;  11/2013 Cyrus Neah cneah@codingreal.com
//**  SharpDBM is an independent reimplementation of the JDBM 1.0 software library in C#  
//*************************************************************************

using System;

namespace SharpDBM.recman
{


///
// *  This class manages physical row ids, and their data.
// 
	internal   class PhysicalRowIdManager
	{

	// The file we're talking to and the associated page manager.
		private RecordFile file;
		private PageManager pageman;
		private FreePhysicalRowIdPageManager freeman;

//    *
//     *  Creates a new rowid manager using the indicated record file.
//     *  and page manager.
//     
 
//
		internal PhysicalRowIdManager(RecordFile file, PageManager pageManager)
		{
			this.file = file;
			this.pageman = pageManager;
			this.freeman = new FreePhysicalRowIdPageManager(file, pageman);
		}

//    *
//     *  Inserts a new record. Returns the new physical rowid.
//     
 
//
		internal virtual Location insert(sbyte[] data, int start, int length)
		{
			Location retval = alloc(length);
			write(retval, data, start, length);
			return retval;
		}

//    *
//     *  Updates an existing record. Returns the possibly changed
//     *  physical rowid.
//     
 
//
		internal virtual Location update(Location rowid, sbyte[] data, int start, int length)
		{
		// fetch the record header
			BlockIo block = file.get(rowid.getBlock());
			RecordHeader head = new RecordHeader(block, rowid.getOffset());
			if (length > head.getAvailableSize())
			{
			// not enough space - we need to copy to a new rowid.
				file.release(block);
				free(rowid);
				rowid = alloc(length);
			}
			else
			{
				file.release(block);
			}

		// 'nuff space, write it in and return the rowid.
			write(rowid, data, start, length);
			return rowid;
		}

//    *
//     *  Deletes a record.
//     
 
//
		internal virtual void delete(Location rowid)
		{
			free(rowid);
		}

//    *
//     *  Retrieves a record.
//     
 
//
		internal virtual sbyte[] fetch(Location rowid)
		{
		// fetch the record header
			PageCursor curs = new PageCursor(pageman, rowid.getBlock());
			BlockIo block = file.get(curs.getCurrent());
			RecordHeader head = new RecordHeader(block, rowid.getOffset());

		// allocate a return buffer
			sbyte[] retval = new sbyte[head.getCurrentSize()];
			if (retval.Length == 0)
			{
				file.release(curs.getCurrent(), false);
				return retval;
			}

		// copy bytes in
			int offsetInBuffer = 0;
			int leftToRead = retval.Length;
			short dataOffset = (short)(rowid.getOffset() + RecordHeader.SIZE);
			while (leftToRead > 0)
			{
			// copy current page's data to return buffer
				int toCopy = RecordFile.BLOCK_SIZE - dataOffset;
				if (leftToRead < toCopy)
				{
					toCopy = leftToRead;
				}
				System.Array.Copy(block.getData(), dataOffset, retval, offsetInBuffer, toCopy);

			// Go to the next block
				leftToRead -= toCopy;
				offsetInBuffer += toCopy;

				file.release(block);

				if (leftToRead > 0)
				{
					block = file.get(curs.next());
					dataOffset = DataPage.O_DATA;
				}

			}

			return retval;
		}

//    *
//     *  Allocate a new rowid with the indicated size.
//     
 
//
		private Location alloc(int size)
		{
			Location retval = freeman.get(size);
			if (retval == null)
			{
				retval = allocNew(size, pageman.getLast(Magic.USED_PAGE));
			}
			return retval;
		}

//    *
//     *  Allocates a new rowid. The second parameter is there to
//     *  allow for a recursive call - it indicates where the search
//     *  should start.
//     
 
//
		private Location allocNew(int size, long start)
		{
			BlockIo curBlock;
			DataPage curPage;
			RecordHeader hdr;
			if (start == 0)
			{
			// we need to create a new page.
				start = pageman.allocate(Magic.USED_PAGE);
				curBlock = file.get(start);
				curPage = DataPage.getDataPageView(curBlock);
				curPage.setFirst(DataPage.O_DATA);
				hdr = new RecordHeader(curBlock, DataPage.O_DATA);
				hdr.setAvailableSize(0);
				hdr.setCurrentSize(0);
			}
			else
			{
				curBlock = file.get(start);
				curPage = DataPage.getDataPageView(curBlock);
			}

		// follow the rowids on this page to get to the last one. We don't
		// fall off, because this is the last page, remember?
			short pos = curPage.getFirst();
			if (pos == 0)
			{
			// page is exactly filled by the last block of a record
				file.release(curBlock);
				return allocNew(size, 0);
			}

			hdr = new RecordHeader(curBlock, pos);
			while (hdr.getAvailableSize() != 0 && pos < RecordFile.BLOCK_SIZE)
			{
				pos += (short)(hdr.getAvailableSize() + RecordHeader.SIZE);
				if (pos == RecordFile.BLOCK_SIZE)
				{
				// Again, a filled page.
					file.release(curBlock);
					return allocNew(size, 0);
				}

				hdr = new RecordHeader(curBlock, pos);
			}

			if (pos == RecordHeader.SIZE)
			{
			// the last record exactly filled the page. Restart forcing
			// a new page.
				file.release(curBlock);
			}

		// we have the position, now tack on extra pages until we've got
		// enough space.
			Location retval = new Location(start, pos);
			int freeHere = RecordFile.BLOCK_SIZE - pos - RecordHeader.SIZE;
			if (freeHere < size)
			{
			// check whether the last page would have only a small bit left.
			// if yes, increase the allocation. A small bit is a record
			// header plus 16 bytes.
				int lastSize = (size - freeHere) % DataPage.DATA_PER_PAGE;
				if ((DataPage.DATA_PER_PAGE - lastSize) < (RecordHeader.SIZE + 16))
				{
					size += (DataPage.DATA_PER_PAGE - lastSize);
				}

			// write out the header now so we don't have to come back.
				hdr.setAvailableSize(size);
				file.release(start, true);

				int neededLeft = size - freeHere;
			// Refactor these two blocks!
				while (neededLeft >= DataPage.DATA_PER_PAGE)
				{
					start = pageman.allocate(Magic.USED_PAGE);
					curBlock = file.get(start);
					curPage = DataPage.getDataPageView(curBlock);
					curPage.setFirst((short) 0); // no rowids, just data
					file.release(start, true);
					neededLeft -= DataPage.DATA_PER_PAGE;
				}
				if (neededLeft > 0)
				{
				// done with whole chunks, allocate last fragment.
					start = pageman.allocate(Magic.USED_PAGE);
					curBlock = file.get(start);
					curPage = DataPage.getDataPageView(curBlock);
					curPage.setFirst((short)(DataPage.O_DATA + neededLeft));
					file.release(start, true);
				}
				}
			else
			{
			// just update the current page. If there's less than 16 bytes
			// left, we increase the allocation (16 bytes is an arbitrary
			// number).
				if (freeHere - size <= (16 + RecordHeader.SIZE))
				{
					size = freeHere;
				}
				hdr.setAvailableSize(size);
				file.release(start, true);
			}
			return retval;

		}


 
//
		private void free(Location id)
		{
		// get the rowid, and write a zero current size into it.
			BlockIo curBlock = file.get(id.getBlock());
			DataPage curPage = DataPage.getDataPageView(curBlock);
			RecordHeader hdr = new RecordHeader(curBlock, id.getOffset());
			hdr.setCurrentSize(0);
			file.release(id.getBlock(), true);

		// write the rowid to the free list
			freeman.put(id, hdr.getAvailableSize());
		}

//    *
//     *  Writes out data to a rowid. Assumes that any resizing has been
//     *  done.
//     
 
//
		private void write(Location rowid, sbyte[] data, int start, int length)
		{
			PageCursor curs = new PageCursor(pageman, rowid.getBlock());
			BlockIo block = file.get(curs.getCurrent());
			RecordHeader hdr = new RecordHeader(block, rowid.getOffset());
			hdr.setCurrentSize(length);
			if (length == 0)
			{
				file.release(curs.getCurrent(), true);
				return;
			}

		// copy bytes in
			int offsetInBuffer = start;
			int leftToWrite = length;
			short dataOffset = (short)(rowid.getOffset() + RecordHeader.SIZE);
			while (leftToWrite > 0)
			{
			// copy current page's data to return buffer
				int toCopy = RecordFile.BLOCK_SIZE - dataOffset;

				if (leftToWrite < toCopy)
				{
					toCopy = leftToWrite;
				}
                sbyte[] sbytes = Array.ConvertAll(data, b => (sbyte)b);
                System.Array.Copy(sbytes, offsetInBuffer, block.getData(), dataOffset, toCopy);

			// Go to the next block
				leftToWrite -= toCopy;
				offsetInBuffer += toCopy;

				file.release(curs.getCurrent(), true);

				if (leftToWrite > 0)
				{
					block = file.get(curs.next());
					dataOffset = DataPage.O_DATA;
				}
			}
		}
	}


}